#ifndef _INSTRUCTION_H_
#define _INSTRUCTION_H_

#include "types.h"

inline word_t imm_U(uint32_t inst) {
    struct U_type {
        uint32_t opcode : 7;
        uint32_t rd : 5;
        int32_t imm_31_12 : 20;
    };
    U_type* inst_U = (U_type*)&inst;
    return inst_U->imm_31_12 << 12;
}

inline word_t imm_J(uint32_t inst) {
    struct J_type {
        uint32_t opcode : 7;
        uint32_t rd : 5;
        uint32_t imm_19_12 : 8;
        uint32_t imm_11 : 1;
        uint32_t imm_10_1 : 10;
        int32_t imm_20 : 1;
    };
    J_type* inst_J = (J_type*)&inst;
    return  (inst_J->imm_20 << 20) | (inst_J->imm_19_12 << 12) | 
            (inst_J->imm_11 << 11) | (inst_J->imm_10_1  << 1 ) ;
}

inline word_t imm_I(uint32_t inst) {
    struct I_type {
        uint32_t opcode : 7;
        uint32_t rd : 5;
        uint32_t funct3 : 3;
        uint32_t rs1 : 5;
        int32_t imm_11_0 : 12;
    };
    I_type* inst_I = (I_type*)&inst;
    return inst_I->imm_11_0;
}

inline word_t imm_B(uint32_t inst) {
    struct B_type {
        uint32_t opcode : 7;
        uint32_t imm_11 : 1;
        uint32_t imm_4_1 : 4;
        uint32_t funct3 : 3;
        uint32_t rs1 : 5;
        uint32_t rs2 : 5;
        uint32_t imm_10_5 : 6;
        int32_t imm_12 : 1;
    };
    B_type* inst_B = (B_type*)&inst;
    return  (inst_B->imm_12 << 12)  | (inst_B->imm_11 << 11) | 
            (inst_B->imm_10_5 << 5) | (inst_B->imm_4_1 << 1) ;
}

inline word_t imm_S(uint32_t inst) {
    struct S_type {
        uint32_t opcode : 7;
        uint32_t imm_4_0 : 5;
        uint32_t funct3 : 3;
        uint32_t rs1 : 5;
        uint32_t rs2 : 5;
        int32_t imm_11_5 : 7;
    };
    S_type* inst_S = (S_type*)&inst;
    return  (inst_S->imm_11_5 << 5) | (inst_S->imm_4_0);
}

enum opcode {
    OPCODE_LUI      = 0b0110111,
    OPCODE_AUIPC    = 0b0010111,
    OPCODE_JAL      = 0b1101111,
    OPCODE_JALR     = 0b1100111,
    OPCODE_BRANCH   = 0b1100011,
    OPCODE_LOAD     = 0b0000011,
    OPCODE_STORE    = 0b0100011,
    OPCODE_OPIMM    = 0b0010011,
    OPCODE_OPIMM32  = 0b0011011,
    OPCODE_OP       = 0b0110011,
    OPCODE_OP32     = 0b0111011,
    OPCODE_SYSTEM   = 0b1110011,
};

enum funct3_branch {
    FUNCT3_BEQ  = 0b000,
    FUNCT3_BNE  = 0b001,
    FUNCT3_BLT  = 0b100,
    FUNCT3_BGE  = 0b101,
    FUNCT3_BLTU = 0b110,
    FUNCT3_BGEU = 0b111
};

enum funct3_load {
    FUNCT3_LB   = 0b000,
    FUNCT3_LH   = 0b001,
    FUNCT3_LW   = 0b010,
    FUNCT3_LD   = 0b011,
    FUNCT3_LBU  = 0b100,
    FUNCT3_LHU  = 0b101,
    FUNCT3_LWU  = 0b110
};

enum funct3_store {
    FUNCT3_SB   = 0b000,
    FUNCT3_SH   = 0b001,
    FUNCT3_SW   = 0b010,
    FUNCT3_SD   = 0b011
};

enum funct3_op {
    FUNCT3_ADD_SUB  = 0b000,
    FUNCT3_SLL      = 0b001,
    FUNCT3_SLT      = 0b010,
    FUNCT3_SLTU     = 0b011,
    FUNCT3_XOR      = 0b100,
    FUNCT3_SRL_SRA  = 0b101,
    FUNCT3_OR       = 0b110,
    FUNCT3_AND      = 0b111
};

enum funct3_m {
    FUNCT3_MUL      = 0b000,
    FUNCT3_MULH     = 0b001,
    FUNCT3_MULHSU   = 0b010,
    FUNCT3_MULHU    = 0b011,
    FUNCT3_DIV      = 0b100,
    FUNCT3_DIVU     = 0b101,
    FUNCT3_REM      = 0b110,
    FUNCT3_REMU     = 0b111
};

enum funct3_system {
    FUNCT3_PRIV     = 0b000,

    FUNCT3_CSRRW    = 0b001,
    FUNCT3_CSRRS    = 0b010,
    FUNCT3_CSRRC    = 0b011,
    
    FUNCT3_CSRRWI   = 0b101,
    FUNCT3_CSRRSI   = 0b110,
    FUNCT3_CSRRCI   = 0b111
};

enum funct7 {
    FUNCT7_NORMAL   = 0b0000000,
    FUNCT7_SUB_SRA  = 0b0100000,
    FUNCT7_MUL      = 0b0000001
};

enum class Instruction {
    LUI, AUIPC,
    JAL, JALR,
    BEQ, BNE, BLT, BGE, BLTU, BGEU,
    LB, LH, LW, LD, LBU, LHU, LWU,
    SB, SH, SW, SD,
    ADDI, SLTI, SLTIU, XORI, ORI, ANDI, SLLI, SRLI, SRAI,
    ADD, SUB, SLL, SLT, SLTU, XOR, SRL, SRA, OR, AND,
    MUL, MULH, MULHSU, MULHU, DIV, DIVU, REM, REMU,
    ECALL, EBREAK, MRET, CSRRW, CSRRS, CSRRC, CSRRWI, CSRRSI, CSRRCI,
    ADDIW, SLLIW, SRLIW, SRAIW, ADDW, SUBW, SLLW, SRLW, SRAW,
    MULW, DIVW, DIVUW, REMW, REMUW
};

#endif
